package componentloader

import (
	"fmt"
	"reflect"
	"sort"

	"zstackgo/spring"
	"zstackgo/todo"

	"github.com/meilihao/golib/v2/log"
)

var (
	_                 PluginRegistryIN = &PluginRegistryImpl{}
	defaultExtemsions map[string][]*PluginExtension
)

type PluginRegistryImpl struct {
	Extemsions                 map[string][]*PluginExtension
	ExtensionsByInterfaceName  map[string][]*PluginExtension
	ExtensionsByInterfaceClass map[reflect.Type][]interface{}
	ExtensionAsMap             map[reflect.Type]map[reflect.Value]reflect.Value
	ExtensionListAsMap         map[reflect.Type]map[reflect.Value][]interface{}
}

func NewPluginRegistryImpl() *PluginRegistryImpl {
	return &PluginRegistryImpl{
		Extemsions:                 defaultExtemsions,
		ExtensionsByInterfaceName:  make(map[string][]*PluginExtension, 30),
		ExtensionsByInterfaceClass: make(map[reflect.Type][]interface{}, 30),
	}
}

func (impl *PluginRegistryImpl) SortPlugins() {
	for _, exts := range impl.ExtensionsByInterfaceName {
		sort.Slice(exts, func(i, j int) bool {
			// greater order means the position is more proceeding in plugin list
			return exts[i].Order > exts[j].Order
		})
	}
}

func (impl *PluginRegistryImpl) BuildPluginTree() {
	loaderI, ok := todo.Set.Load(todo.KeyPlatformLoader)
	if !ok {
		log.Glog.Fatal("not found loader")
	}

	loader := loaderI.(ComponentLoader)
	for _, entry := range impl.Extemsions {
		/*
		 * If instance-id is specified in extension declaration, find
		 * that bean used as extension implementation. Otherwise, use
		 * parent plugin as implementation
		 */

		var instance interface{}
		var beanName string
		for _, ext := range entry {
			if ext.InstanceId != "" {
				//log.Glog.Debug("instance impl interface", zap.String("instance", ext.InstanceId), zap.String("interface", ext.ReferenceInterface))

				beanName = ext.InstanceId
				instance = loader.GetComponentByBeanName(ext.InstanceId)
			} else {
				//log.Glog.Debug("instance impl interface", zap.String("instance", ext.BeanName), zap.String("interface", ext.ReferenceInterface))

				beanName = ext.BeanName
				instance = loader.GetComponentByBeanName(ext.BeanName)
			}
			ext.Instance = instance

			if !reflect.TypeOf(ext.Instance).Implements(spring.GlobalMyContainer.GetTypeByInterfaceName(ext.ReferenceInterface)) {
				log.Glog.Fatal(fmt.Sprintf("%s is not an instance of the interface %s",
					beanName, ext.ReferenceInterface))
			}

			exts := impl.ExtensionsByInterfaceName[ext.ReferenceInterface]
			if exts == nil {
				exts = make([]*PluginExtension, 0)
			}
			exts = append(exts, ext)

			impl.ExtensionsByInterfaceName[ext.ReferenceInterface] = exts
		}
	}
}

func (impl *PluginRegistryImpl) Initialize() {
	impl.BuildPluginTree()
	impl.ContinueBuildTreeFromDSL()
	impl.SortPlugins()
	impl.CreateClassPluginInstanceMap()
	log.Glog.Info("Plugin system has been initialized successfully")
}

func (impl *PluginRegistryImpl) ContinueBuildTreeFromDSL() {
	// 没找到使用了PluginDSL的地方, so do nothing
}

func (impl *PluginRegistryImpl) CreateClassPluginInstanceMap() {
	for className, exts := range impl.ExtensionsByInterfaceName {
		clazz := spring.GlobalMyContainer.GetTypeByInterfaceName(className)

		var instances []interface{}
		for _, ext := range exts {
			if !instancesIsContains(instances, ext) {
				instances = append(instances, ext.Instance)
			}
		}

		impl.ExtensionsByInterfaceClass[clazz] = instances
	}
}

func instancesIsContains(instances []interface{}, ext interface{}) bool {
	for i := range instances {
		if instances[i] == ext { // match type and value
			return true
		}
	}

	return false
}

func (impl *PluginRegistryImpl) GetExtensionList(name string) []interface{} {
	return impl.ExtensionsByInterfaceClass[spring.GlobalMyContainer.GetTypeByInterfaceName(name)]

}
